using System;
using System.Diagnostics;
using System.Linq;
using Kaleida.ServiceMonitor.Model.Runtime;
using log4net;

namespace Kaleida.ServiceMonitor.Model
{
    public class Monitor
    {
        private static readonly ILog log = LogManager.GetLogger(typeof (Monitor));
        private static readonly ILog resultsLog = LogManager.GetLogger("ResultsLog");

        private readonly ScriptWorkspace workspace = new ScriptWorkspace();
        private readonly MonitorState state = new MonitorState();

        internal event EventHandler<OperationSuccessfulEventArgs> OperationSuccessful;
        internal event EventHandler<OperationFailedEventArgs> OperationFailed;
        internal event EventHandler ErrorsAcknowledged;
        internal event EventHandler<ScriptCompilationCompleteEventArgs> ScriptCompilationComplete;

        private readonly LimitedList<RequestResult> recentResponses;
        private readonly LimitedList<RequestResult> recentErrors;
        private readonly OperationStatisticsMap statistics = new OperationStatisticsMap();

        private readonly object responseProcessingLock = new object();
        private readonly IConfiguration configuration;

        public Monitor(IConfiguration configuration)
        {
            this.configuration = configuration;

            recentResponses = new LimitedList<RequestResult>("Response", configuration.MaximumResponses);
            recentErrors = new LimitedList<RequestResult>("Error", configuration.MaximumErrors);

            workspace.CurrentScriptChanged += (s, e) => TryCompileCurrentScript();
            workspace.CurrentScriptContentChanged += (s, e) => TryCompileCurrentScript();

            state.MonitorStarted += (s, e) => statistics.Start();
        }

        public ScriptWorkspace Workspace
        {
            get { return workspace; }
        }

        public MonitorState State
        {
            get { return state; }
        }

        public OperationStatisticsMap Statistics
        {
            get { return statistics; }
        }

        private void TryCompileCurrentScript()
        {
            var scriptSource = workspace.CurrentScript;
            var compilation = scriptSource.Compile();

            if (ScriptCompilationComplete != null)
                ScriptCompilationComplete(this, new ScriptCompilationCompleteEventArgs(compilation));

            state.ScriptCompiled(compilation);
        }

        public LimitedList<RequestResult> RecentResponses
        {
            get { return recentResponses; }
        }

        public LimitedList<RequestResult> RecentErrors
        {
            get { return recentErrors; }
        }

        public IConfiguration Configuration
        {
            get { return configuration; }
        }

        public void InvokeNextOperation()
        {
            var currentCompilation = State.ScriptCompilation;
            if (currentCompilation == null || !currentCompilation.Succeeded || !currentCompilation.CompiledScript.Operations.Any())
                return;

            var operation = currentCompilation.CompiledScript.ExecutionOrder.SelectItem(currentCompilation.CompiledScript.Operations);
            log.DebugFormat("Calling operation {0}", operation.Description);

            var sentOn = DateTime.Now;

            try
            {
                var sw = Stopwatch.StartNew();
                var response = operation.Invoke();
                sw.Stop();

                string responseSummary = operation.ProcessResponse(response) ?? "";
                var result = new RequestSuccessResult(operation, sentOn, sw.Elapsed, responseSummary);

                OperationCompletedSuccessfully(operation, result);
            }
            catch (Exception exception)
            {
                var result = new RequestErrorResult(operation, sentOn, exception);
                OperationFailureOccurred(operation, result);
            }
        }

        private void OperationCompletedSuccessfully(IOperation operation, RequestSuccessResult result)
        {
            lock (responseProcessingLock)
            {
                Statistics.IncrementSuccessCount(operation, result.RequestDuration);

                AddResult(result);

                if (OperationSuccessful != null)
                    OperationSuccessful(this, new OperationSuccessfulEventArgs(operation, result));
            }
        }

        private void OperationFailureOccurred(IOperation operation, RequestErrorResult result)
        {
            lock (responseProcessingLock)
            {
                Statistics.IncrementFailureCount(operation);

                AddResult(result);

                var error = new OperationError(result.Exception.Message, DateTime.Now);
                State.FlagError(error);

                if (OperationFailed != null)
                    OperationFailed(this, new OperationFailedEventArgs(operation, result));
            }
        }

        private void AddResult(RequestResult request)
        {
            resultsLog.Info(request.ToClipboardItemText());

            recentResponses.AddToTop(request);
            
            if (request is RequestErrorResult)
            {
                recentErrors.AddToTop(request);
            }
        }

        public void AcknowledgeErrors()
        {
            State.ClearErrors();

            if (ErrorsAcknowledged != null)
                ErrorsAcknowledged(this, EventArgs.Empty);
        }
    }
}
